package org.zoomdev.zoom.dao.ddl.utils;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.dao.ddl.SqlType;
import org.zoomdev.zoom.dao.driver.MysqlDriver;
import org.zoomdev.zoom.dao.meta.ColumnMeta;
import org.zoomdev.zoom.dao.meta.IndexMeta;
import org.zoomdev.zoom.dao.meta.TableMeta;
import org.zoomdev.zoom.dao.naming.NameMapping;
import org.zoomdev.zoom.dao.naming.impl.CamelNameMapping;
import org.zoomdev.zoom.dao.naming.impl.DetectPrefixNameMappingFactory;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

public class DDLClassGenerator {


    private String outputPath;

    private String basePackage;

    private TableMeta tableMeta;
    private NameMapping nameMapping;


    public DDLClassGenerator() {

    }

    public DDLClassGenerator mapping(String mapping) {
        NameMapping nameMapping;
        if (mapping.equals("auto")) {
            String[] columns = Arrays.stream(tableMeta.getColumns()).map(ColumnMeta::getName)
                    .toArray(String[]::new);
            nameMapping = DetectPrefixNameMappingFactory.DEFAULT.create(columns);
        } else {
            nameMapping = CamelNameMapping.DEFAULT;
        }
        this.nameMapping = nameMapping;
        return this;
    }

    public DDLClassGenerator table(TableMeta table) {
        this.tableMeta = table;
        return this;
    }


    public DDLClassGenerator outputPath(String outputPath) {
        this.outputPath = outputPath;
        return this;
    }

    public DDLClassGenerator basePackage(String basePackage) {
        this.basePackage = basePackage;
        return this;
    }

    public DDLClassGenerator generate() {
        try {
            String remarks = tableMeta.getComment();
            String tableName = tableMeta.getName();
            String engine = tableMeta.getEngine();

            String fullOutputPath = Paths.get(outputPath,
                    basePackage.replace('.', File.separatorChar)).toString();
            // 2. 生成类名
            String className = "_DDL_" + NameUtils.toPascalCase(tableName);

            // 3. 创建输出文件
            Path outputFile = Paths.get(fullOutputPath, className + ".java");
            Files.createDirectories(outputFile.getParent());

            try (Writer writer = new OutputStreamWriter(
                    Files.newOutputStream(outputFile.toFile().toPath()),
                    StandardCharsets.UTF_8
            )) {

                ColumnMeta[] list = tableMeta.getColumns();
                // 4. 生成包声明
                writer.write("package " + basePackage + ";\n\n");

                // 5. 生成导入


                Map<String, String> uniques = getSinglePart(tableMeta, true);
                Map<String, String> indexes = getSinglePart(tableMeta, false);
                Set<String> primaryKeys =
                        !ArrayUtils.isEmpty(tableMeta.getPrimaryKeys()) && tableMeta.getPrimaryKeys().length == 1 ?
                                Arrays.stream(tableMeta.getPrimaryKeys()).collect(Collectors.toSet()) :
                                new HashSet<>();


                List<String> imports = new ArrayList<>();
                imports.add("import org.zoomdev.zoom.dao.ddl.SqlType;\n");
                imports.add("import org.zoomdev.zoom.dao.ddl._DDL_Column;\n");

                if (!ArrayUtils.isEmpty(tableMeta.getPrimaryKeys()) && tableMeta.getPrimaryKeys().length > 1) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_CompoundPrimary;\n");
                }

                if (hasCompoundUnique(tableMeta)) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_CompoundUnique;\n");
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_CompoundUniqueGroup;\n");
                }

                if (hasCompoundIndex(tableMeta)) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_CompoundIndex;\n");
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_CompoundIndexGroup;\n");
                }

                if (!indexes.isEmpty()) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_Index;\n");
                }


                if (!primaryKeys.isEmpty()) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_Primary;\n");
                }

                imports.add("import org.zoomdev.zoom.dao.ddl._DDL_Table;\n");

                if (!uniques.isEmpty()) {
                    imports.add("import org.zoomdev.zoom.dao.ddl._DDL_Unique;\n");
                }

                if (imports.size() < 5) {
                    for (String i : imports) {
                        writer.write(i);
                    }
                } else {
                    writer.write("import org.zoomdev.zoom.dao.ddl.*;\n");
                }


                if (!StringUtils.isEmpty(engine)) {
                    writer.write("import org.zoomdev.zoom.dao.driver.MysqlDriver;\n");
                }

                writer.write("\n");


                if (!ArrayUtils.isEmpty(tableMeta.getPrimaryKeys()) && tableMeta.getPrimaryKeys().length > 1) {
                    writer.write("@_DDL_CompoundPrimary(columns = {");
                    String[] cols = tableMeta.getPrimaryKeys();
                    for (int i = 0; i < cols.length; i++) {
                        writer.write("\"" + cols[i] + "\"");
                        if (i < cols.length - 1) {
                            writer.write(", ");
                        }
                    }
                    writer.write("})\n");
                }

                if (tableMeta.getIndexes() != null) {
                    handleIndex(writer, tableMeta);
                }


                // 6. 生成表注解
                writer.write("@_DDL_Table(name = \"" + tableName + "\", " +
                        "simpleName = \"\", ");
                if (!StringUtils.isEmpty(engine)) {
                    writer.write("engine = MysqlDriver." + engine + ", ");
                }

                writer.write("comment = \"" + (remarks != null ? remarks : "") + "\")\n");

                // 7. 生成类定义
                writer.write("public class " + className + " {\n\n");
                for (ColumnMeta columnMeta : list) {
                    generateField(columnMeta, nameMapping, writer, primaryKeys, uniques, indexes);
                }
                writer.write("}\n");
            }
            return this;
        } catch (Throwable e) {
            throw new ZoomException(e);
        }
    }


    private static Map<String, String> getSinglePart(TableMeta tableMeta, boolean unique) {
        Map<String, String> indexMetaMap = new HashMap<>();
        Arrays.stream(tableMeta.getIndexes())
                .filter(i -> i.getColumns().length == 1 && i.isUnique() == unique)
                .forEach(m -> {
                    indexMetaMap.put(m.getColumns()[0], m.getName());
                });
        return indexMetaMap;
    }

    private static boolean hasCompoundUnique(TableMeta meta) {
        return Arrays.stream(meta.getIndexes()).filter(IndexMeta::isUnique).anyMatch(i -> i.getColumns().length > 1);
    }

    private static boolean hasCompoundIndex(TableMeta meta) {
        return Arrays.stream(meta.getIndexes()).filter(i -> !i.isUnique())
                .anyMatch(i -> i.getColumns().length > 1);
    }

    private static void handleIndex(Writer writer, TableMeta tableMeta) throws IOException {

        List<IndexMeta> compoundIndexes = getMultiPart(tableMeta, false);
        List<IndexMeta> compoundUniques = getMultiPart(tableMeta, true);

        if (!compoundUniques.isEmpty()) {
            writer.write("@_DDL_CompoundUniqueGroup({\n");
            for (int i = 0; i < compoundUniques.size(); i++) {
                IndexMeta index = compoundUniques.get(i);
                writer.write("        @_DDL_CompoundUnique(name = \"" + index.getName() + "\", columns = {");
                String[] cols = index.getColumns();
                for (int j = 0; j < cols.length; j++) {
                    writer.write("\"" + cols[j] + "\"");
                    if (j < cols.length - 1) {
                        writer.write(", ");
                    }
                }
                writer.write("})");
                if (i < compoundUniques.size() - 1) {
                    writer.write(",");
                }
                writer.write("\n");
            }
            writer.write("})\n");
        }

        if (!compoundIndexes.isEmpty()) {
            writer.write("@_DDL_CompoundIndexGroup({\n");
            for (int i = 0; i < compoundIndexes.size(); i++) {
                IndexMeta index = compoundIndexes.get(i);
                writer.write("        @_DDL_CompoundIndex(name = \"" + index.getName() + "\", columns = {");
                String[] cols = index.getColumns();
                for (int j = 0; j < cols.length; j++) {
                    writer.write("\"" + cols[j] + "\"");
                    if (j < cols.length - 1) {
                        writer.write(", ");
                    }
                }
                writer.write("})");
                if (i < compoundIndexes.size() - 1) {
                    writer.write(",");
                }
                writer.write("\n");
            }
            writer.write("})\n");
        }
    }

    private static void generateField(ColumnMeta columnMeta,
                                      NameMapping nameMapping,
                                      Writer writer,
                                      Set<String> primaryKeys,
                                      Map<String, String> uniques,
                                      Map<String, String> indexes) throws IOException {
        String columnName = columnMeta.getName();
        long columnSize = columnMeta.getSize();
        int decimalDigits = columnMeta.getScale();
        boolean notNull = columnMeta.isNotNull();
        String columnRemarks = columnMeta.getComment();
        SqlType sqlType = columnMeta.getType();
        Class<?> javaType = sqlType.getJavaType();

        if (primaryKeys.contains(columnName)) {
            writer.write("    @_DDL_Primary()\n");
        }

        if (uniques.containsKey(columnName)) {
            String keyName = uniques.get(columnName);
            writer.write("    @_DDL_Unique(name = \"" + keyName + "\")\n");
        }

        if (indexes.containsKey(columnName)) {
            String keyName = indexes.get(columnName);
            writer.write("    @_DDL_Index(name = \"" + keyName + "\")\n");
        }

        // 生成字段注解
        writer.write("    @_DDL_Column(name = \"" + columnName + "\", type = SqlType." + sqlType + ", ");

        if (columnSize > 0 && (sqlType.hasLength() || sqlType.hasScale())) {
            writer.write("size = " + columnSize + ", ");
        }

        if (decimalDigits > 0) {
            writer.write("scale = " + decimalDigits + ", ");
        }

        if (notNull) {
            writer.write("notNull = true, ");
        }

        if (columnMeta.getValues() != null) {
            writer.write("values = {");
            boolean first = true;
            for (String str : columnMeta.getValues()) {
                if (first) first = false;
                else writer.write(", ");
                writer.write("\"");
                writer.write(str);
                writer.write("\"");
            }
            writer.write("}, ");
        }

        if (columnMeta.isAutoIncrement()) {
            writer.write("autoIncrement = true, ");
        }

        if (columnMeta.getDefaultValue() != null) {
            writer.write(generateDefaultValue(columnMeta.getType(), columnMeta.getDefaultValue()));
        }

        writer.write("comment = \"" + (columnRemarks != null ? columnRemarks : "") + "\"");
        writer.write(")\n");

        // 生成字段定义
        writer.write("    private " + getJavaType(javaType) + " " + nameMapping.getMapping(columnName) + ";\n\n");
    }


    private static List<IndexMeta> getMultiPart(TableMeta tableMeta, boolean unique) {
        return Arrays.stream(tableMeta.getIndexes())
                .filter(i -> i.getColumns().length > 1 && i.isUnique() == unique)
                .collect(Collectors.toList());
    }

    private static String generateDefaultValue(SqlType type, String defaultValue) {
        if (type.isString()) {
            return "defaultValue = \"" + defaultValue + "\", ";
        }
        if (MysqlDriver.CURRENT_TIMESTAMP.equals(defaultValue)) {
            return "defaultValue = MysqlDriver.CURRENT_TIMESTAMP, ";
        } else if (MysqlDriver.CURRENT_TIMESTAMP_ON_UPDATE.equals(defaultValue)) {
            return "defaultValue = MysqlDriver.CURRENT_TIMESTAMP_ON_UPDATE, ";
        } else {
            return "defaultValue = " + defaultValue + ", ";
        }

    }

    private static String getJavaType(Class<?> javaType) {
        if (javaType.isArray()) {
            return javaType.getComponentType().getName() + "[]";
        }

        if (javaType.getName().startsWith("java.lang")) {
            return javaType.getSimpleName();
        }
        return javaType.getName();
    }
}
